import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

# 读入训练数据
train = np.loadtxt('../data/data1.txt', delimiter=',', dtype='float', skiprows=1)
train_x = train[:, 0]
train_y = train[:, 1]
# 训练集与测试集划分 数据 4 / 1
X_train, X_test, y_train, y_test = train_test_split(train_x, train_y,
                                                    test_size=0.2, random_state=0)


# 极差标准化方法 预处理数据
def scaler(train, test):  # 定义归一化函数，进行数据压缩
    min = train.min(axis=0)  # 训练集最小值
    max = train.max(axis=0)  # 训练集最大值
    gap = max - min  # 最大值和最小值的差
    train -= min  # 所有数据减最小值
    train /= gap  # 所有数据除以大小值差
    test -= min  # 把训练集最小值应用于测试集
    test /= gap  # 把训练集大小值差应用于测试集
    return train, test  # 返回压缩后的数据


X_train, X_test = scaler(X_train, X_test)  # 对特征数据值归一化
y_train, y_test = scaler(y_train, y_test)  # 对标签数据值归一化


# plot方法显示训练集数据散点图
# plt.plot(X_train, y_train, 'r.', label='Training data')
# plt.xlabel('X_train')  # x轴Label
# plt.ylabel('y_train')  # y轴Label
# plt.legend()  # 显示图例
# plt.show()  # 显示绘图结果

# 定义一个MSE均方误差函数
def loss_function(X, y, weight, bias):
    y_hat = weight * X + bias  # 这是假设函数,其中已经应用了Python的广播功能
    loss = y_hat - y  # 求出每一个y’和训练集中真实的y之间的差异
    cost = np.sum(loss ** 2) / (2 * len(X))  # 这是均方误差函数的代码实现
    return cost  # 返回当前模型的均方误差值


def gradient_descent(X, y, w, b, lr, iter):  # 定义一个实现梯度下降的函数
    l_history = np.zeros(iter)  # 初始化记录梯度下降过程中损失的数组
    w_history = np.zeros(iter)  # 初始化记录梯度下降过程中权重的数组
    b_history = np.zeros(iter)  # 初始化记录梯度下降过程中偏置的数组
    for i in range(iter):  # 进行梯度下降的迭代，就是下多少级台阶
        y_hat = w * X + b  # 这个是向量化运行实现的假设函数
        loss = y_hat - y  # 这是中间过程,求得的是假设函数预测的y和真正的y值间的差值

        derivative_w = X.T.dot(loss) / len(X)  # 对权重求导, len(X)是样本总数
        derivative_b = sum(loss) * 1 / len(X)  # 对偏置求导
        # derivative_w = X.T.dot(loss)  # 对权重求导, len(X)是样本总数
        # derivative_b = sum(loss) * 1  # 对偏置求导

        w = w - lr * derivative_w  # 结合下降速率alpha更新权重
        b = b - lr * derivative_b  # 结合下降速率alpha更新偏置
        l_history[i] = loss_function(X, y, w, b)  # 梯度下降过程中损失的历史
        w_history[i] = w  # 梯度下降过程中权重的历史
        b_history[i] = b  # 梯度下降过程中偏置的历史
    return l_history, w_history, b_history  # 返回梯度下降过程数据


# 首先确定参数的初始值
iterations = 300  # 迭代300次
alpha = 0.5  # 此处初始学习速率设为0.5
weight = -5  # 权重
bias = 3  # 偏置

# 根据初始参数值，进行梯度下降，也就是开始训练机器，拟合函数
loss_history, weight_history, bias_history = gradient_descent(
    X_train, y_train, weight, bias, alpha, iterations)

plt.plot(loss_history, 'g--', label='Loss Curve')
plt.xlabel('Iterations')  # x轴Label
plt.ylabel('Loss')  # y轴Label
plt.legend()  # 显示图例
plt.show()  # 显示损失曲线

# 绘制当前的函数模型
plt.plot(X_train, y_train, 'r.', label='Training data')  # 显示训练集散点图
line_X = np.linspace(X_train.min(), X_train.max(), 500)  # X值域
# 关于weight_history[-1],这里的索引[-1]，就代表迭代500次后的最后一个W值
line_y = [weight_history[-1] * xx + bias_history[-1] for xx in line_X]  # 假设函数
plt.plot(line_X, line_y, 'b--', label='Current hypothesis')  # 显示当前函数
plt.xlabel('X_train')  # x轴Label
plt.ylabel('y_train')  # y轴Label
plt.legend()  # 显示图例
plt.show()  # 显示函数图像

print('当前损失：', loss_function(X_train, y_train,
                             weight_history[-1], bias_history[-1]))

W = weight_history[-1]
print('当前权重：', W)
B = bias_history[-1]
print('当前偏置：', B)

print('测试集损失：', loss_function(X_test, y_test,
                              weight_history[-1], bias_history[-1]))

print("新样本点(20.341)")
print("预测值", W * 20.341 + B)
